Linux 下的 GPIO 编程

GPIO(General-purpose input/output) 通用型之输入输出的简称。在嵌入式系统中,经常需要控制许多结构简单的外部设备或者电路,这些设备有的需要通过 CPU 控制,有的需要 CPU 提供输入信号。并且,许多设备或电路只要求有开/关两种状态就够了,比如 LED 的亮与灭。对这些设备的控制,使用传统的串口或者并口就显得比较复杂,所以,在嵌入式微处理器上通常提供了一种“通用可编程 I/O 端口”,也就是 GPIO。

Linux 下的 GPIO 设备

配置 Pin MUX

通过配置寄存器可以定义 I/O 管脚功能,在使用 GPIO 功能时,首先要将相应的引脚配置为 GPIO,管脚寄存器和配置功能参考《PAD config registers》 手册。

  • 下面以 J176 上的 SPI2_CSN0,SPI2_CLK,SPI2_MOSI,SPI2_MISO,SPI2_CSN1 做简单说明。

    Port_J176

  • 引脚功能定义:MUX_CFG_002 (address:0x3fe830334)

    MUX_CFG_002

  • 设置 SPI2_CLK 为GPIO0_34

    # 读取 0x3fe830334 数值
    root@thead-910:~# memtool md -l 0x3fe830334
    3fe830334: 00005014 00000000 00400000 00000000                .P........@.....
    3fe830344: 00000000 00000000 00000000 00000000                ................
    
    # 将 bit21:20 置为 ’01‘,写回寄存器
    root@thead-910:~# memtool mw -l 0x3fe830334 0x105014
    

    注:如果提示没有 memtool 命令,使用 apt install -y memtool 安装。

GPIO 设备

通过 sysfs 方式控制 GPIO,先访问 /sys/class/gpio 目录,向 export 文件写入 GPIO 编号,使得该 GPIO 的操作接口从内核空间暴露到用户空间,GPIO 的操作接口包括 direction 和 value 等,direction 控制 GPIO 方向,而 value 可控制 GPIO 输出或获得 GPIO 输入。文件 IO 方式操作 GPIO,使用到了 4个函数 open、close、read、write。通过:ls -l /sys/class/gpio 可能以查看 GPIO的信息:

root@thead-910:~# ls -l /sys/class/gpio
total 0
--w------- 1 root root 4096 Jan  6 13:33 export
lrwxrwxrwx 1 root root    0 Dec 26 20:33 gpiochip352 -> ../../devices/platform/soc/3fff72000.gpio/gpio/gpiochip352
lrwxrwxrwx 1 root root    0 Dec 26 20:33 gpiochip384 -> ../../devices/platform/soc/3fff72000.gpio/gpio/gpiochip384
lrwxrwxrwx 1 root root    0 Dec 26 20:33 gpiochip416 -> ../../devices/platform/soc/3fff72000.gpio/gpio/gpiochip416
lrwxrwxrwx 1 root root    0 Dec 26 20:33 gpiochip448 -> ../../devices/platform/soc/3fff71000.gpio/gpio/gpiochip448
lrwxrwxrwx 1 root root    0 Dec 26 20:33 gpiochip480 -> ../../devices/platform/soc/3fff71000.gpio/gpio/gpiochip480
--w------- 1 root root 4096 Dec 26 20:33 unexport

文件说明:

  1. gpio_operation 通过 /sys/ 文件接口操作 IO 端口 GPIO 到文件系统的映射
  2. 控制 GPIO 的目录位于 /sys/class/gpio
  3. /sys/class/gpio/export 文件用于通知系统需要导出控制的 GPIO 引脚编号
  4. /sys/class/gpio/unexport 用于通知系统取消导出
  5. /sys/class/gpio/gpiochipXXX 目录保存系统中 GPIO 寄存器的信息,包括每个寄存器控制引脚的起始编号 base,寄存器名称,引脚总数

dts 中的定义分别为:

        gpio0: gpio@3fff71000 {
            compatible = "snps,dw-apb-gpio";
            reg = <0x3 0xfff71000 0x0 0x1000>;
            #address-cells = <1>;
            #size-cells = <0>;

            /* GPIO0[0-31] */
            gpio0_porta: gpio0-controller@0 {
                compatible = "snps,dw-apb-gpio-port";
                gpio-controller;
                #gpio-cells = <2>;
                snps,nr-gpios = <32>;
                reg = <0>;

                interrupt-controller;
                #interrupt-cells = <2>;
                interrupt-parent = <&intc>;
                interrupts = <27>;
            };

            /* GPIO0[32-63] */
            gpio0_portb: gpio0-controller@1 {
                compatible = "snps,dw-apb-gpio-port";
                gpio-controller;
                #gpio-cells = <2>;
                snps,nr-gpios = <32>;
                reg = <1>;
            };
        };

        gpio1: gpio@3fff72000 {
            compatible = "snps,dw-apb-gpio";
            reg = <0x3 0xfff72000 0x0 0x1000>;
            #address-cells = <1>;
            #size-cells = <0>;

            /* GPIO1[0-31] */
            gpio1_porta: gpio1-controller@0 {
                compatible = "snps,dw-apb-gpio-port";
                gpio-controller;
                #gpio-cells = <2>;
                snps,nr-gpios = <32>;
                reg = <0>;
            };

            /* GPIO1[32-63] */
            gpio1_portb: gpio1-controller@1 {
                compatible = "snps,dw-apb-gpio-port";
                gpio-controller;
                #gpio-cells = <2>;
                snps,nr-gpios = <32>;
                reg = <1>;
            };

            /* GPIO1[64-95] */
            gpio1_portc: gpio1-controller@2 {
                compatible = "snps,dw-apb-gpio-port";
                gpio-controller;
                #gpio-cells = <2>;
                snps,nr-gpios = <32>;
                reg = <2>;
            };
        };

gpiochipxxx 与 gpio 的对应关系:

gpiochip352:gpio1_portc,GPIO1[64-95] 分别对应为 352 ~ 383 号
gpiochip384:gpio1_portb,GPIO1[32-63] 分别对应为 384 ~ 415 号
gpiochip416:gpio1_porta,GPIO1[ 0-31] 分别对应为 416 ~ 447 号
gpiochip448:gpio0_portb,GPIO0[32-63] 分别对应为 448 ~ 479 号
gpiochip480:gpio0_porta,GPIO0[ 0-31] 分别对应为 480 ~ 511

GPIO 导出

需要对一个GPIO 控制,首先需要对 GPIO 进行导出,操作步骤如下:

第一步:确定 GPIO 引脚编号

引脚编号 = 控制引脚的寄存器基数 + 控制引脚寄存器位数

例如:(具体 GPIO 需要参考数据手册),如果使想用 SPI2_CLK(GPIO0_34),那么引脚编号就可能等于 448 + (34 - 32) = 450

第二步:导出引脚

向 /sys/class/gpio/export 写入此编号,比如 450 号引脚,在 shell 中可以通过以下命令实现:

# 导出 450号 GPIO
echo 450 > /sys/class/gpio/export

# 查看导出是否成功
ls /sys/class/gpio/gpio450

命令成功后生成 /sys/class/gpio/gpio450 目录,如果没有出现相应的目录,说明此引脚不可导出。

第三步:设置引导方向

通过 GPIO 的 direction文件,修改 GPIO 引脚的方向:

echo out > /sys/class/gpio/gpio450/direction

direction 接受的参数可以是:in、out、high、low。其中参数 high / low 在设置方向为输出的同时,将 value 设置为相应的 1 / 0。

第四步:设置或者查看 GPIO电平

GPIO 的 value 文件是端口的数值,为1或0:

# 设置 GPIO 电平
echo 1 > /sys/class/gpio/gpio450/value

# 查看 GPIO 电平
cat /sys/class/gpio/gpio450/value

第五步:取消GPIO 导出

echo 450 > /sys/class/gpio/unexport

编程示例

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>   //define O_WRONLY and O_RDONLY

static int write_file(const char *filename, const char *value)
{
    int fd = open(filename, O_WRONLY);
    if(fd != 0) {
        write(fd, value,strlen(value));
        close(fd);
        return 0;
    }

    return EXIT_FAILURE;
}

// 打开 GPIO
static int gpio_open(char *id)
{
    return write_file("/sys/class/gpio/export", id);
}

// 设置 GPIO 方向
static int gpio_direction(char *id, char *dir)
{
    char filename[128];
    sprintf(filename, "/sys/class/gpio/gpio%s/direction", id);

    return write_file(filename, dir);
}

// 设置 GPIO 电平
static int gpio_set(char *id, char *dir)
{
    char filename[128];
    sprintf(filename, "/sys/class/gpio/gpio%s/value", id);

    return write_file(filename, dir);
}

#define GPIO_PIN_450      "450"
int main(int argc, char **argv)
{
    if (gpio_open(GPIO_PIN_450) == 0) {
        gpio_direction(GPIO_PIN_450, "OUT");

        while(1) {
            gpio_set(GPIO_PIN_450, "1");
            usleep(1000000);
            gpio_set(GPIO_PIN_450, "0");
            usleep(1000000);
        }
    }

    return 0;
}

可以使用SHELL 脚本对GPIO的控制:

#!/bin/bash

echo 450 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio450/direction
while :
do
    echo 1 > /sys/class/gpio/gpio450/value
    sleep 1
    echo 0 > /sys/class/gpio/gpio450/value
    sleep 1
done

注:如果没有 sleep 命令,使用 apt install -y coreutils 安装。

results matching ""

    No results matching ""